﻿using Jinndev;
using Jinndev.Utils;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

namespace Assets.Scripts.UI.Upload
{

    public class SyncUploader : IUploader
    {

        public event Action<object> Log;
        public event Action<int, int, long> Progress;
        public event Action<double> SubProgress;
        public event Action Complete;
        public bool uploading { get; set; }

        private Thread thread;
        private HttpClient httpClient;

        private UploadConfig config;
        private string localPath;

        public SyncUploader(UploadConfig config, string localPath)
        {
            this.config = config;
            this.localPath = localPath;
        }

        public void Dispose()
        {
            httpClient?.Dispose();
            httpClient = null;

            if (thread != null)
            {
                thread.Abort();
                thread = null;
            }
        }

        public void StartUpload()
        {
            if (thread != null)
            {
                thread.Abort();
                thread = null;
            }
            thread = new Thread(Upload);
            thread.Start();
        }

        private void Upload(object state)
        {
            httpClient = new HttpClient();

            string[] files = Directory.GetFiles(localPath, "*", SearchOption.AllDirectories);

            Log("上传: " + config.localPath + " 到 " + config.remotePath);

            // 排个序
            List<string> fileList = new List<string>(files.Length);
            for (int i = 0; i < files.Length; i++)
            {
                string file = files[i];
                if (file.EndsWith(".origin.js"))
                    continue; // 跳过混淆源文件
                if (file.StartsWith("~"))
                    continue; // 跳过指定文件

                if (file.EndsWith(".html"))
                    fileList.Add(file);
                else
                    fileList.Insert(0, file);
            }
            // 过滤掉检查已存在
            CheckExists(fileList);

            long totalSize = 0;
            for (int i = 0; i < fileList.Count; i++)
            {
                string file = fileList[i];

                Progress(i, fileList.Count, totalSize);

                try
                {
                    SubProgress(0);
                    UploadFile(file);
                    totalSize += CommonUtil.GetFileSize(file);
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                    Log(e);
                }
            }
            Progress(fileList.Count, fileList.Count, totalSize);
            SubProgress(1);

            httpClient.Dispose();
            httpClient = null;
            thread = null;

            Log("完成");
            Complete();
        }

        private void CheckExists(List<string> fileList)
        {
            var checkUrl = $"http://{config.host}:{config.port}/Upload/CheckExists?sign={config.sign}";

            Dictionary<string, string> fileHashs = new();
            foreach (var file in fileList)
            {
                var relativePath = GetRelativeFile(file);
                string hash = HashUtil.CalculateFileHash(file);
                fileHashs[relativePath] = hash;
            }

            var args = new Dictionary<string, object>();
            args["rootDir"] = config.remotePath;
            args["fileHashs"] = fileHashs;

            HttpResponseMessage res = null;
            string text = null;
            try
            {
                // 将数据放在请求体中
                var content = new StringContent(JsonConvert.SerializeObject(args), Encoding.UTF8, "application/json");
                res = httpClient.PostAsync(checkUrl, content).Result;
                text = res.Content.ReadAsStringAsync().Result;
                res.EnsureSuccessStatusCode();
            }
            catch (Exception e)
            {
                Log(e.Message + ", statusCode: " + res?.StatusCode + ", " + text);
            }

            HashSet<string> existFiles = JsonConvert.DeserializeObject<HashSet<string>>(text);
            Log("已存在文件数: " + existFiles.Count);

            //Debug.Log("A: " + fileList.Count);
            for (int i = fileList.Count - 1; i >= 0; i--)
            {
                var file = fileList[i];
                var relativePath = GetRelativeFile(file);

                if (existFiles.Contains(relativePath))
                {
                    //Debug.Log("移除: " + file);
                    fileList.RemoveAt(i);
                }
            }
            //Debug.Log("B: " + fileList.Count);
        }

        private string GetRelativeFile(string localFile)
        {
            var relativePath = config.remotePath + localFile.Substring(localPath.Length).Replace("\\", "/");
            if (relativePath.StartsWith("/"))
            {
                relativePath = relativePath.Substring(1);
            }
            return relativePath;
        }

        private void UploadFile(string localFile)
        {
            // 构造上传文件的相对路径
            var relativePath = GetRelativeFile(localFile);
            string hash = HashUtil.CalculateFileHash(localFile);

            var uploadUrl = $"http://{config.host}:{config.port}/Upload/Upload?sign={config.sign}";
            Log("上传: " + relativePath);
            using (var fileStream = new FileStream(localFile, FileMode.Open, FileAccess.Read))
            {
                // 创建一个 MultipartFormDataContent 用于上传文件
                using (var content = new MultipartFormDataContent())
                {
                    // 将文件流添加到内容中
                    var streamContent = new ProgressableStreamContent(fileStream);
                    streamContent.ProgressChanged += SubProgress;

                    content.Add(streamContent, hash, relativePath);

                    HttpResponseMessage response = null;
                    try
                    {
                        response = httpClient.PostAsync(uploadUrl, content).Result;
                        response.EnsureSuccessStatusCode();
                    }
                    catch (Exception e)
                    {
                        Log(e.Message + ", statusCode: " + response?.StatusCode);
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// 删除服务器上的多余文件
        /// </summary>
        public void StartDeleteExcludeFiles()
        {
            if (thread != null)
            {
                thread.Abort();
                thread = null;
            }
            thread = new Thread(DeleteExcludeFiles);
            thread.Start();
        }

        public void DeleteExcludeFiles()
        {
            httpClient = new HttpClient();

            string[] files = Directory.GetFiles(localPath, "*", SearchOption.AllDirectories);
            HashSet<string> fileSet = new HashSet<string>(files);

            foreach (var file in files)
            {
                // 构造上传文件的相对路径
                var relativePath = file.Substring(localPath.Length).Replace("\\", "/");
                if (relativePath.StartsWith("/"))
                {
                    relativePath = relativePath.Substring(1);
                }
                fileSet.Add(relativePath);
            }

            var deleteUrl = $"http://{config.host}:{config.port}/Upload/DeleteExclude?sign={config.sign}";
            var args = new Dictionary<string, object>();
            args["rootDir"] = config.remotePath;
            args["excludeFiles"] = fileSet;

            Progress(0, 0, 0);
            SubProgress(0);

            HttpResponseMessage res = null;
            string result = null;
            try
            {
                // 将数据放在请求体中
                var content = new StringContent(JsonConvert.SerializeObject(args), Encoding.UTF8, "application/json");
                res = httpClient.PostAsync(deleteUrl, content).Result;
                result = res.Content.ReadAsStringAsync().Result;
                res.EnsureSuccessStatusCode();
            }
            catch (Exception e)
            {
                Log(e.Message + ", statusCode: " + res?.StatusCode + ", " + result);
                Complete();
                return;
            }

            DeleteResult deleteResult = JsonConvert.DeserializeObject<DeleteResult>(result);

            foreach (string remoteFile in deleteResult.deleteFiles)
            {
                Log("删除远程文件: " + remoteFile);
            }
            Log("完成");

            httpClient.Dispose();
            httpClient = null;
            thread = null;

            Progress(deleteResult.deleteFiles.Count, deleteResult.deleteFiles.Count, deleteResult.totalSize);
            SubProgress(1);
            Complete();
        }

        private class DeleteResult
        {
            public List<string> deleteFiles { get; set; }
            public long totalSize { get; set; }
        }

        internal class ProgressableStreamContent : StreamContent
        {

            public event Action<double> ProgressChanged;

            private Stream content;

            public ProgressableStreamContent(Stream content) : base(content)
            {
                this.content = content;
            }

            protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context)
            {
                if (content == null) throw new InvalidOperationException("content is null.");

                var buffer = new byte[4096];
                long totalLength = content.Length;
                double currentLength = 0;

                using (var reader = new StreamReader(content))
                {
                    while (true)
                    {
                        int bytesRead = await reader.BaseStream.ReadAsync(buffer, 0, buffer.Length);
                        if (bytesRead == 0) break;

                        currentLength += bytesRead;
                        if (ProgressChanged != null)
                        {
                            double progress = currentLength / totalLength;
                            ProgressChanged.Invoke(progress);
                        }

                        await stream.WriteAsync(buffer, 0, bytesRead);
                        await stream.FlushAsync();
                    }
                }
            }

        }

    }

}
